package com.xinqi.modules.exam.student.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.ImmutableList;
import com.xinqi.common.base.enums.YesNoEnum;
import com.xinqi.common.base.error.CommonErrors;
import com.xinqi.common.base.response.PageInfo;
import com.xinqi.common.base.response.Param;
import com.xinqi.common.base.response.R;
import com.xinqi.common.base.utils.number.BigDecimals;
import com.xinqi.common.base.utils.spring.Spring;
import com.xinqi.common.core.utils.StringUtils;
import com.xinqi.common.mybatis.utils.Pages;
import com.xinqi.common.mybatis.utils.Queries;
import com.xinqi.common.redis.topic.annotation.TopicRegister;
import com.xinqi.common.redis.utils.Caches;
import com.xinqi.modules.course.clazz.client.ClassStudentClient;
import com.xinqi.modules.course.clazz.dto.rsp.ClassStudentResultDTO;
import com.xinqi.modules.exam.common.assistant.ChatAssistantExamService;
import com.xinqi.modules.exam.common.assistant.enums.MessageSubTypeEnum;
import com.xinqi.modules.exam.common.constants.ExamConstants;
import com.xinqi.modules.exam.common.constants.ExamI18nConstants;
import com.xinqi.modules.exam.common.delayed.ExamTopicEventService;
import com.xinqi.modules.exam.common.enums.ExamQueueEnum;
import com.xinqi.modules.exam.common.error.ExamErrors;
import com.xinqi.modules.exam.common.manager.CommonClassManager;
import com.xinqi.modules.exam.paper.convert.CourseExamPaperConvert;
import com.xinqi.modules.exam.paper.domain.CourseExamPaperConfigEntity;
import com.xinqi.modules.exam.paper.domain.CourseExamPaperEntity;
import com.xinqi.modules.exam.paper.dto.rsp.CourseExamPaperBaseDTO;
import com.xinqi.modules.exam.paper.dto.rsp.CourseExamPaperStatsDataDTO;
import com.xinqi.modules.exam.paper.enums.ExamStatusEnum;
import com.xinqi.modules.exam.paper.enums.IsPublishScoreEnum;
import com.xinqi.modules.exam.paper.manager.CourseExamPaperConfigManager;
import com.xinqi.modules.exam.paper.manager.CourseExamPaperManager;
import com.xinqi.modules.exam.paper.manager.CourseExamPaperStatsManager;
import com.xinqi.modules.exam.student.convert.CourseExamPaperStudentConvert;
import com.xinqi.modules.exam.student.domain.CourseExamPaperErrorSetEntity;
import com.xinqi.modules.exam.student.domain.CourseExamPaperStudentEntity;
import com.xinqi.modules.exam.student.dto.PaperAnswerDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentBatchDelDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentCreateDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentDeleteDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentManualDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentMarkDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentQueryDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentRejectDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentStartDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentSubmitDTO;
import com.xinqi.modules.exam.student.dto.req.CourseExamPaperStudentUpdateDTO;
import com.xinqi.modules.exam.student.dto.rsp.CourseExamPaperExtractResultDTO;
import com.xinqi.modules.exam.student.dto.rsp.CourseExamPaperStudentDetailDTO;
import com.xinqi.modules.exam.student.dto.rsp.CourseExamPaperStudentListDTO;
import com.xinqi.modules.exam.student.dto.rsp.CourseExamPaperStudentResultDTO;
import com.xinqi.modules.exam.student.enums.PaperAnswerStatusEnum;
import com.xinqi.modules.exam.student.enums.PaperErrorSetTypeEnum;
import com.xinqi.modules.exam.student.manager.CourseExamPaperErrorSetManager;
import com.xinqi.modules.exam.student.manager.CourseExamPaperStudentManager;
import com.xinqi.modules.exam.student.service.CourseExamPaperStudentService;
import com.xinqi.modules.exam.utils.MarkQuestionUtils;
import com.xinqi.modules.user.user.dto.rsp.UserBaseInfoDO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * 课程测评学生答题服务层实现
 *
 * @author: linkai
 * @date: 2023/07/02
 */
@Slf4j
@Validated
@TopicRegister
@Service("courseExamPaperStudentService")
@RequiredArgsConstructor
public class CourseExamPaperStudentServiceImpl implements CourseExamPaperStudentService {
    private final RedissonClient redissonClient;
    private final CommonClassManager commonClassManager;
    @Lazy
    private final ExamTopicEventService examTopicEventService;
    private final CourseExamPaperManager courseExamPaperManager;
    private final CourseExamPaperConvert courseExamPaperConvert;
    private final ChatAssistantExamService chatAssistantExamService;
    private final CourseExamPaperStudentConvert courseExamPaperStudentConvert;
    private final CourseExamPaperStatsManager courseExamPaperStatsManager;
    private final CourseExamPaperStudentManager courseExamPaperStudentManager;
    private final CourseExamPaperConfigManager courseExamPaperConfigManager;
    private final CourseExamPaperErrorSetManager courseExamPaperErrorSetManager;

    private final ClassStudentClient classStudentClient;

    /**
     * 分页查询 课程测评学生答题数据
     */
    @Override
    @DS("slaver")
    public PageInfo<CourseExamPaperStudentResultDTO> page(Param pageable, CourseExamPaperStudentQueryDTO query) {
        IPage<CourseExamPaperStudentEntity> page = courseExamPaperStudentManager.page(Pages.page(pageable), wrapper(query));
        return Pages.convert(page, courseExamPaperStudentConvert::convert);
    }

    /**
     * 查询 课程测评学生答题数据
     */
    @Override
    public List<CourseExamPaperStudentResultDTO> find(CourseExamPaperStudentQueryDTO query) {
        return courseExamPaperStudentManager.list(wrapper(query)).stream()
            .map(courseExamPaperStudentConvert::convert).collect(Collectors.toList());
    }

    private LambdaQueryWrapper<CourseExamPaperStudentEntity> wrapper(CourseExamPaperStudentQueryDTO query) {
        LambdaQueryWrapper<CourseExamPaperStudentEntity> wrapper = Queries.lambda();
        Queries.accept(query.getId(), v -> wrapper.eq(CourseExamPaperStudentEntity::getId, v));
        return wrapper;
    }

    /**
     * 根据id查询 课程测评学生答题数据
     */
    @Override
    public CourseExamPaperStudentResultDTO findById(Long id) {
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(id), "ID不能为空");
        return Optional.ofNullable(courseExamPaperStudentManager.findById(id)).map(courseExamPaperStudentConvert::convert).orElse(null);
    }

    /**
     * 新增 课程测评学生答题数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long insert(@Valid CourseExamPaperStudentCreateDTO dto) {
        CourseExamPaperStudentEntity entity = courseExamPaperStudentConvert.create(dto);
        courseExamPaperStudentManager.save(entity);
        return entity.getId();
    }

    /**
     * 根据id修改 课程测评学生答题数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateById(@Valid CourseExamPaperStudentUpdateDTO dto) {
        Optional.ofNullable(courseExamPaperStudentManager.getById(dto.getId()))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("数据不存在"));
        CourseExamPaperStudentEntity entity = courseExamPaperStudentConvert.update(dto);
        return courseExamPaperStudentManager.updateById(entity);
    }

    /**
     * 根据id删除 课程测评学生答题数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteById(Long id) {
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(id), "ID不能为空");
        return courseExamPaperStudentManager.removeById(id);
    }

    /**
     * 根据id删除 课程测评学生答题数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteById(CourseExamPaperStudentDeleteDTO dto) {
        return deleteById(dto.getId());
    }

    /**
     * 生成学生试卷'/
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generateStudentPaper(Long examId) {
        log.info("生成学生试卷，开始。。。examId:{}", examId);
        // 0. 检查是否已经生成
        CommonErrors.BAD_REQUEST.check(!courseExamPaperStudentManager.isGenerated(examId), "学生试卷已生成");
        // 1. 获取试卷信息
        CourseExamPaperEntity exam = getExamPaper(examId);
        if (Objects.equals(exam.getStatus(), ExamStatusEnum.已结束)) {
            throw CommonErrors.BAD_REQUEST.asException("测评状态错误");
        }
        CourseExamPaperConfigEntity config = Optional.ofNullable(courseExamPaperConfigManager.findByExamId(examId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评配置不存在"));
        // 2. 获取学生信息
        List<Long> studentIds = commonClassManager.findStudentIdByClassId(exam.getClassId());
        // 3. 生成学生试卷
        generateStudentPaper(exam, config, studentIds);
        log.info("生成学生试卷，结束！examId:{}", examId);
    }

    @Transactional(rollbackFor = Exception.class)
    public void generateStudentPaper(CourseExamPaperEntity exam, CourseExamPaperConfigEntity config, List<Long> studentIds) {
        // 1. 生成学生试卷
        if (CollectionUtils.isEmpty(studentIds)) {
            throw CommonErrors.BAD_REQUEST.asException("该班级下没有学生");
        }
        List<CourseExamPaperStudentEntity> studentPaperList = studentIds.stream()
            .map(studentId -> courseExamPaperStudentConvert.create(exam, config, studentId))
            .collect(Collectors.toList());
        // 2. 批量保存试卷
        courseExamPaperStudentManager.saveBatch(studentPaperList);
        // 3. 更新统计信息
        updateStatsData(exam.getId());
    }

    /**
     * 是否能考试
     */
    @Override
    public Boolean isCanPaper(Long examId) {
        ExamStatusEnum examStatus = Optional.ofNullable(courseExamPaperManager.findStatusById(examId))
            .orElseThrow(() -> ExamErrors.EXAM_NOT_EXIST.asException(ExamI18nConstants.测评不存在));
        return Objects.equals(examStatus, ExamStatusEnum.已开始);
    }

    /**
     * 是否能考试
     */
    @Override
    public Boolean isCanPaper(ExamStatusEnum es) {
        ExamStatusEnum examStatus = Optional.ofNullable(es)
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评状态不能为空"));
        return Objects.equals(examStatus, ExamStatusEnum.已开始);
    }

    /**
     * 是否交卷
     */
    private Boolean isCanPaper(ExamStatusEnum es, CourseExamPaperStudentEntity entity) {
        ExamStatusEnum examStatus = Optional.ofNullable(es)
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评状态不能为空"));
        if (Objects.equals(examStatus, ExamStatusEnum.已开始)) {
            return true;
        }
        // 在测评结束后的一分钟内可以交卷
        return Objects.equals(examStatus, ExamStatusEnum.已结束)
            && entity.getAnswerEndTime().plusMinutes(ExamConstants.END_DELAY_SUBMIT_TIMES).isBefore(LocalDateTime.now());
    }

    /**
     * 是否能交卷
     */
    @Override
    public Boolean isCanSubmit(Long examId, CourseExamPaperStudentEntity entity) {
        CommonErrors.BAD_REQUEST.check(isCanPaper(examId), "测评状态错误");
        return Objects.equals(entity.getIsSubmit(), YesNoEnum.NO.ordinal());
    }

    /**
     * 是否能交卷
     */
    @Override
    public Boolean isCanSubmit(ExamStatusEnum examStatus, CourseExamPaperStudentEntity entity) {
        CommonErrors.BAD_REQUEST.check(isCanPaper(examStatus, entity), "测评状态错误");
        return Objects.equals(entity.getIsSubmit(), YesNoEnum.NO.ordinal());
    }

    /**
     * 获取试题明细
     */
    @Override
    public CourseExamPaperStudentDetailDTO findPaperDetail(Long id, boolean isShowAnswer) {
        CourseExamPaperStudentEntity entity = Optional.ofNullable(courseExamPaperStudentManager.findById(id))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("学生试卷不存在"));
        // 获取测评试题
        return findPaperDetail(entity, isShowAnswer);
    }

    /**
     * 获取试题明细
     */
    @Override
    public CourseExamPaperStudentDetailDTO findPaperDetail(Long examId, Long studentId, boolean isShowAnswer) {
        CourseExamPaperStudentEntity entity = getStudentPaper(examId, studentId);
        CourseExamPaperEntity examPaper = getExamPaper(entity.getExamPaperId());
        // 获取测评试题
        return findPaperDetail(entity, examPaper, isShowAnswer);
    }

    @Override
    public CourseExamPaperStudentDetailDTO findCurrentPaperDetail(Long examId, Long studentId) {
        CourseExamPaperStudentEntity entity = Optional.ofNullable(courseExamPaperStudentManager.findByExamIdAndStudentId(examId, studentId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("暂不支持后加入学生参与考试"));
        CourseExamPaperEntity examPaper = getExamPaper(entity.getExamPaperId());
        CourseExamPaperConfigEntity config = Optional.ofNullable(courseExamPaperConfigManager.findByExamId(examId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评配置不存在"));
//        boolean isShowAnswer = Objects.equals(config.getIsPublishAnswer(), IsPublishAnswerEnum.到截止时间公布)
//            && Objects.equals(examPaper.getStatus(), ExamStatusEnum.已结束);
        CourseExamPaperStudentDetailDTO paperDetail = findPaperDetail(entity, examPaper, true);
        // 获取学生的答题信息
        Map<Long, List<CourseExamPaperErrorSetEntity>> errorSetMap = courseExamPaperErrorSetManager.findByExamIdAndStudentId(examId, studentId)
            .stream().collect(Collectors.groupingBy(CourseExamPaperErrorSetEntity::getQuestionId));
        // 设置题目
        paperDetail.getPaperAnswer().forEach(v -> {
            List<CourseExamPaperErrorSetEntity> errorSetEntities = errorSetMap.get(v.getQuestionId());
            if (CollectionUtils.isEmpty(errorSetEntities)) {
                return;
            }
            for (CourseExamPaperErrorSetEntity errorSet : errorSetEntities) {
                if (Objects.equals(errorSet.getType(), PaperErrorSetTypeEnum.错题集)) {
                    v.setErrorId(errorSet.getId());
                } else {
                    v.setStarId(errorSet.getId());
                }
            }
        });
        return paperDetail;
    }

    /**
     * 获取试题明细
     */
    private CourseExamPaperStudentDetailDTO findPaperDetail(
        CourseExamPaperStudentEntity entity,
        boolean isShowAnswer
    ) {
        return findPaperDetail(entity, getExamPaper(entity.getExamPaperId()), isShowAnswer);
    }

    /**
     * 获取试题明细
     */
    private CourseExamPaperStudentDetailDTO findPaperDetail(
        CourseExamPaperStudentEntity entity,
        CourseExamPaperEntity examPaper,
        boolean isShowAnswer
    ) {
        // 获取测评试题
        CourseExamPaperStudentDetailDTO detail = courseExamPaperStudentConvert.toDetail(entity, examPaper);

        // 获取学生信息-并赋值返回信息
        ClassStudentResultDTO student = R.as(classStudentClient.findByStudentId(entity.getClassId(), entity.getStudentId()));
        detail.setAvatar(student.getAvatar());
        detail.setStudentName(student.getStudentName());

        // 获取测评明细
        CourseExamPaperBaseDTO examPaperBaseResult = courseExamPaperConvert.baseInfo(examPaper);
        detail.setExamBaseInfo(examPaperBaseResult);
        // 是否不显示答案
        if (!isShowAnswer) {
            detail.getPaperAnswer().forEach(v -> {
                Optional.ofNullable(v.getQuestion()).ifPresent(q -> {
                    q.setAnswer(null);
                    q.setAnswerFile(null);
                    q.setAnswerFiles(null);
                    q.setAnalysis(null);
                    q.setAnalysisFile(null);
                    q.setAnalysisFiles(null);
                    Optional.ofNullable(q.getOptionList()).ifPresent(op ->
                        op.forEach(o1 -> o1.setSelected(null))
                    );
                });
            });
        }
        return detail;
    }

    @Override
    public CourseExamPaperEntity getExamPaper(Long examId) {
        return Optional.ofNullable(courseExamPaperManager.findById(examId))
            .orElseThrow(() -> ExamErrors.EXAM_NOT_EXIST.asException(ExamI18nConstants.测评不存在));
    }

    @Override
    public CourseExamPaperStudentEntity getStudentPaper(Long examId, Long studentId) {
        return Optional.ofNullable(courseExamPaperStudentManager.findByExamIdAndStudentId(examId, studentId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("学生试卷不存在"));
    }

    /**
     * 开始答题
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CourseExamPaperStudentDetailDTO startPaper(@Valid CourseExamPaperStudentStartDTO dto) {
        Long examId = dto.getExamPaperId();
        Long studentId = dto.getStudentId();
        CourseExamPaperEntity examPaper = getExamPaper(examId);
        CourseExamPaperStudentEntity entity = getStudentPaper(examId, studentId);
        CommonErrors.BAD_REQUEST.check(Objects.equals(entity.getIsExam(), YesNoEnum.NO.ordinal()), "学生已考试");
        // 校验学生是否能考试
        CommonErrors.BAD_REQUEST.check(isCanSubmit(examId, entity), "学生已考试");
        // 如果学生未开始考试，则更新学生试卷状态
        LocalDateTime now = LocalDateTime.now();
        CourseExamPaperStudentEntity temp = new CourseExamPaperStudentEntity();
        temp.setId(entity.getId());
        temp.setIsExam(YesNoEnum.YES.ordinal());
        // 最后一次开始时间
        temp.setLastStartTime(now);
        // 第一次考试时间
        if (entity.getStartTime() == null) {
            temp.setStartTime(now);
        }
        // 如果是限时考试，则根据剩余答题时间计算答题结束时间
        if (Objects.equals(entity.getIsLimitAnswerTime(), YesNoEnum.YES.ordinal())) {
            if (entity.getRemainingAnswerTime() < 1) {
                throw CommonErrors.BAD_REQUEST.asException("答题时间结束，无法参与考试");
            }
            LocalDateTime answerEndTime = now.plusMinutes(entity.getRemainingAnswerTime());
            temp.setAnswerEndTime(answerEndTime.isAfter(examPaper.getEndTime()) ? examPaper.getEndTime() : answerEndTime);
        } else {
            // 如果是非限时考试，则答题结束时间为测评结束时间
            temp.setAnswerEndTime(examPaper.getEndTime());
        }
        // 更新试题
        courseExamPaperStudentManager.updateById(temp);
        entity = courseExamPaperStudentManager.findById(entity.getId());
        return findPaperDetail(entity, false);
    }

    /**
     * 提交试卷
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean submitPaper(@Valid CourseExamPaperStudentSubmitDTO dto) {
        Long examId = dto.getExamPaperId();
        Long studentId = dto.getStudentId();
        CourseExamPaperEntity examPaper = getExamPaper(examId);
        CourseExamPaperConfigEntity config = Optional.ofNullable(courseExamPaperConfigManager.findByExamId(examId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评配置不存在"));
        CourseExamPaperStudentEntity entity = getStudentPaper(examId, studentId);
        // 校验是否能提交
        LocalDateTime now = LocalDateTime.now();
        checkSubmit(examPaper, entity, now);
        CourseExamPaperStudentEntity temp = new CourseExamPaperStudentEntity();
        temp.setId(entity.getId());
        temp.setStatus(PaperAnswerStatusEnum.未批阅);
        temp.setIsExam(YesNoEnum.YES.ordinal());
        temp.setIsSubmit(YesNoEnum.YES.ordinal());
        temp.setSubmitNum(entity.getSubmitNum() != null ? entity.getSubmitNum() + 1 : 1);
        temp.setSubmitTime(now);
        // 是否限时答题
        if (Objects.equals(config.getIsLimitAnswerTime(), YesNoEnum.YES.ordinal())) {
            // 当前时间不能超过考试结束时间
            if (now.isAfter(entity.getAnswerEndTime().plusMinutes(ExamConstants.END_DELAY_SUBMIT_TIMES))) {
                throw CommonErrors.BAD_REQUEST.asException("考试时间已结束");
            }
            // 计算考试时长
            Long examDuration = calcExamDuration(entity.getLastStartTime(), now);
            // 计算剩余时间
            long remainingAnswerTime = entity.getRemainingAnswerTime() - examDuration;
            temp.setRemainingAnswerTime(remainingAnswerTime);
        }
        // 是否强制收卷
        if (Objects.equals(dto.getIsForceSubmit(), Boolean.TRUE)) {
            temp.setIsForceSubmit(YesNoEnum.YES.ordinal());
        }
        // 设置答案
        List<PaperAnswerDTO> answerList;
        if (CollectionUtils.isNotEmpty(dto.getPaperAnswer())) {
            // 设置答案
            answerList = renderAnswer(dto.getPaperAnswer(), entity.getPaperAnswer(), (submitAnswer, studentAnswer) -> {
                studentAnswer.setIsReply(StringUtils.isNotBlank(submitAnswer.getStudentAnswer()));
                studentAnswer.setStudentAnswer(submitAnswer.getStudentAnswer());
                studentAnswer.setStudentAnswerFiles(submitAnswer.getStudentAnswerFiles());
            });
        } else {
            // 交白卷，设置为空
            answerList = entity.getPaperAnswer();
            answerList.forEach(studentAnswer -> {
                studentAnswer.setIsReply(false);
                studentAnswer.setStudentAnswer(null);
                studentAnswer.setStudentAnswerFiles(null);
            });
        }
        temp.setPaperAnswer(answerList);
        // 自动计算分数
        MarkQuestionUtils.MarkQuestionData markQuestionData = MarkQuestionUtils
            .markStudentAnswer(examPaper.getQuestionContent(), answerList, config);
        // 如果全部是客观题，则自动批阅
        if (markQuestionData.getIsAllObjective()) {
            temp.setStatus(PaperAnswerStatusEnum.已批阅);
            temp.setIsMarked(YesNoEnum.YES.ordinal());
            temp.setIsSysMarked(YesNoEnum.YES.ordinal());
            temp.setScore(markQuestionData.getScore());
            temp.setQuestionRightNum(markQuestionData.getQuestionRightNum());
            // 设置总分
            temp.setScore(answerList.stream().map(PaperAnswerDTO::getStudentScore).reduce(BigDecimal.ZERO, BigDecimal::add));
        }
        courseExamPaperStudentManager.updateById(temp);
        // 更新测评统计数据
        updateStatsData(examId);
        return true;
    }

    private void checkSubmit(CourseExamPaperEntity examPaper, CourseExamPaperStudentEntity entity, LocalDateTime now) {
        CommonErrors.BAD_REQUEST.check(Objects.equals(entity.getIsSubmit(), YesNoEnum.NO.ordinal()), "学生已交卷");
        ExamStatusEnum examStatus = examPaper.getStatus();
        if (Objects.equals(examStatus, ExamStatusEnum.已开始)) {
            return;
        }
        // 在测评结束后的一分钟内可以交卷
        LocalDateTime answerEndTime = entity.getAnswerEndTime().isAfter(examPaper.getEndTime()) ? examPaper.getEndTime() : entity.getAnswerEndTime();
        if (Objects.equals(examStatus, ExamStatusEnum.已结束)
            && now.isBefore(answerEndTime.plusMinutes(ExamConstants.END_DELAY_SUBMIT_TIMES))) {
            return;
        }
        throw CommonErrors.BAD_REQUEST.asException("测评未开始或已结束");
    }

    /**
     * 计算考试时长 (分钟)
     */
    private Long calcExamDuration(LocalDateTime lastStartTime, LocalDateTime now) {
        return Duration.between(lastStartTime, now).toMinutes();
    }

    /**
     * 批阅试卷 - 老师
     * <p>批阅只是针对已交卷的学生</p>
     * <p>批阅后，学生不能再次交卷</p>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean markPaper(@Valid CourseExamPaperStudentMarkDTO dto) {
        Long examId = dto.getExamPaperId();
//        CourseExamPaperEntity examPaper = getExamPaper(examId);
        CourseExamPaperStudentEntity entity = getStudentPaper(examId, dto.getStudentId());
        CommonErrors.BAD_REQUEST.check(
            Objects.equals(entity.getIsSubmit(), YesNoEnum.YES.ordinal()),
            "学生试卷状态错误");
        CourseExamPaperStudentEntity temp = new CourseExamPaperStudentEntity();
        temp.setId(entity.getId());
        temp.setStatus(PaperAnswerStatusEnum.已批阅);
        temp.setIsMarked(YesNoEnum.YES.ordinal());
        temp.setIsSysMarked(YesNoEnum.NO.ordinal());
        temp.setScore(BigDecimal.ZERO);
        temp.setQuestionRightNum(0);
        // 设置批阅答案
        List<PaperAnswerDTO> answerList = renderAnswer(dto.getPaperAnswer(), entity.getPaperAnswer(), (teacherAnswer, studentAnswer) -> {
            // 校验批阅分数
            CommonErrors.BAD_REQUEST.check(teacherAnswer.getStudentScore() != null, "批阅分数不能为空");
            CommonErrors.BAD_REQUEST.check(teacherAnswer.getStudentScore().compareTo(BigDecimal.ZERO) >= 0, "批阅分数必须大于或等于0分");
            // 设置批阅分数，批阅分数 不能 大于 题目分数
            studentAnswer.setStudentScore(BigDecimals.formatScore(BigDecimals.min(studentAnswer.getScore(), teacherAnswer.getStudentScore())));
            // 累加 总分
            temp.setScore(temp.getScore().add(studentAnswer.getStudentScore()));
            // 判断是否答对，批阅分数 与 题目分数 相等，则答对
            studentAnswer.setIsRight(studentAnswer.getScore().compareTo(studentAnswer.getStudentScore()) == 0);
            studentAnswer.setTeacherRemark(teacherAnswer.getTeacherRemark());
            // 累加 答对数量
            if (studentAnswer.getIsRight()) {
                temp.setQuestionRightNum(temp.getQuestionRightNum() + 1);
            }
        });
        temp.setPaperAnswer(answerList);
        // 更新数据
        boolean b = courseExamPaperStudentManager.updateById(temp);
        // 更新测评统计数据
        updateStatsData(examId);
        // 发送测评成绩
        CourseExamPaperConfigEntity config = Optional.ofNullable(courseExamPaperConfigManager.findByExamId(examId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评配置不存在"));
        sendScore(config, IsPublishScoreEnum.批阅后公布, ImmutableList.of(dto.getStudentId()));
        return b;
    }

    /**
     * 渲染答案
     */
    private static List<PaperAnswerDTO> renderAnswer(List<PaperAnswerDTO> newPaperAnswer, List<PaperAnswerDTO> paperAnswer, BiConsumer<PaperAnswerDTO, PaperAnswerDTO> consumer) {
        // 渲染答案
        Map<Long, PaperAnswerDTO> map = newPaperAnswer.stream().collect(Collectors.toMap(PaperAnswerDTO::getQuestionId, v -> v));
        paperAnswer.forEach(v -> {
            PaperAnswerDTO teacherAnswer = map.get(v.getQuestionId());
            if (teacherAnswer != null) {
                consumer.accept(teacherAnswer, v);
            }
        });
        return paperAnswer;
    }


    /**
     * 打回试卷 - 老师
     * <p>打回后，学生可以继续提交试卷</p>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean rejectPaper(@Valid CourseExamPaperStudentRejectDTO dto) {
        CourseExamPaperEntity examPaper = getExamPaper(dto.getExamPaperId());
        CommonErrors.BAD_REQUEST.check(!Objects.equals(examPaper.getStatus(), ExamStatusEnum.已结束), "测评结束，打回失败！");
        List<Long> studentIdList = dto.getStudentIdList();
        if (CollectionUtils.isEmpty(studentIdList)) {
            return false;
        }
        List<Long> rejectStudentIdList = courseExamPaperStudentManager.findRejectPaper(dto.getExamPaperId(), studentIdList);
        boolean b = courseExamPaperStudentManager.updateRejectPaper(dto.getExamPaperId(), rejectStudentIdList);
        if (b) {
            // 更新测评统计数据
            updateStatsData(dto.getExamPaperId());
            Spring.committedAsync(() ->
                chatAssistantExamService.sendMessage(dto.getExamPaperId(), rejectStudentIdList, MessageSubTypeEnum.打回)
            );
        }
        return b;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStatsData(Long examId) {
        CourseExamPaperStatsDataDTO stats01 = courseExamPaperStudentManager.findStats01(examId);
        courseExamPaperStatsManager.initStatsData(examId, stats01);
    }

    /**
     * 一键催缴 - 老师
     * <p>催缴只是针对未交卷的学生</p>
     */
    @Override
    public Boolean remindPaper(Long examId) {
        // 限制 1 分钟内只能催缴一次
        CommonErrors.BAD_REQUEST.check(
            Caches.setIfAbsent("1", 60, redissonClient, ExamConstants.LIMIT_REMIND_KEY, examId),
            "一分钟内只能催交一次！");

        // 获取测评数据
        CourseExamPaperEntity entity = courseExamPaperManager.findById(examId);
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(entity), "测评内容不存在");

        // 获取未交学生ID
        List<Long> studentIdByNotPaper = courseExamPaperStudentManager.findStudentIdByNotPaper(examId);
        // 根据主学生ID获取关系用户成员ID
        List<ClassStudentResultDTO> relationshipStudent = R.as(classStudentClient.findClassRelationshipStudentList(entity.getClassId(), studentIdByNotPaper));
        List<Long> relationshipStudentIds = relationshipStudent.stream().map(item -> item.getStudentId()).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(relationshipStudentIds)) {
            return true;
        }

        Spring.committedAsync(() -> {
            chatAssistantExamService.sendRYunMessage(examId, relationshipStudentIds, MessageSubTypeEnum.催交);
            chatAssistantExamService.sendMessage(examId, relationshipStudentIds, MessageSubTypeEnum.催交);
        });
        return true;
    }

    /**
     * 强制收卷 - 老师
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean forcePaper(Long examId) {

        // 获取测评数据
        CourseExamPaperEntity entity = courseExamPaperManager.findById(examId);
        CommonErrors.BAD_REQUEST.check(Objects.nonNull(entity), "测评内容不存在");

        // 获取未交学生ID
        List<Long> studentIdByNotPaper = courseExamPaperStudentManager.findStudentIdByNotPaper(examId);
        // 根据主学生ID获取关系用户成员ID
        if (CollectionUtils.isNotEmpty(studentIdByNotPaper)) {
            List<ClassStudentResultDTO> relationshipStudent = R.as(classStudentClient.findClassRelationshipStudentList(entity.getClassId(), studentIdByNotPaper));
            List<Long> relationshipStudentIds = relationshipStudent.stream().map(item -> item.getStudentId()).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(relationshipStudentIds)) {
                return true;
            }

            Spring.committedAsync(() ->
                chatAssistantExamService.sendRYunMessage(examId, relationshipStudentIds, MessageSubTypeEnum.强制交卷)
            );
        }

        return true;
    }

    @Override
    public List<CourseExamPaperStudentResultDTO> findList(CourseExamPaperStudentQueryDTO dto) {
        return courseExamPaperStudentManager.findList(dto);
    }

    @Override
    public List<CourseExamPaperStudentListDTO> findStudentList(CourseExamPaperStudentQueryDTO dto) {
        return courseExamPaperStudentManager.findStudentList(dto);
    }

    @Override
    @DS("slaver")
    public PageInfo<CourseExamPaperStudentListDTO> pageStudentList(Param pageable, CourseExamPaperStudentQueryDTO dto) {
        IPage<CourseExamPaperStudentListDTO> page = courseExamPaperStudentManager.pageStudentList(Pages.page(pageable), dto);
        return Pages.convert(page);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean screenSwitch(Long examId, Long studentId) {
        CommonErrors.BAD_REQUEST.check(examId != null || studentId != null, "参数错误");
        CourseExamPaperStudentEntity studentPaper = getStudentPaper(examId, studentId);
        CourseExamPaperStudentEntity temp = new CourseExamPaperStudentEntity();
        temp.setId(studentPaper.getId());
        Integer screenSwitchNum = studentPaper.getScreenSwitchNum();
        temp.setScreenSwitchNum(screenSwitchNum == null ? 1 : screenSwitchNum + 1);
        return courseExamPaperStudentManager.updateById(temp);
    }

    @Override
    public CourseExamPaperExtractResultDTO extractAnswer(Long examId, Long questionId) {
        CourseExamPaperStudentEntity entity = courseExamPaperStudentManager.findRandStudent(examId);
        if (entity == null) {
            return null;
        }
        Long userId = entity.getStudentId();
        UserBaseInfoDO user = Optional.ofNullable(commonClassManager.findUserInfoByUserId(userId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("学生信息不存在"));
        CourseExamPaperExtractResultDTO result = new CourseExamPaperExtractResultDTO();
        String answer = entity.getPaperAnswer().stream()
            .filter(v -> Objects.equals(v.getQuestionId(), questionId) && v.getStudentAnswer() != null)
            .map(PaperAnswerDTO::getStudentAnswer).findFirst()
            .orElse(null);
        result.setId(user.getId());
        result.setUsername(user.getUsername());
        result.setNickname(user.getNickname());
        result.setAvatar(user.getAvatar());
        result.setXinqiNumber(user.getXinqiNumber());
        result.setAnswer(answer);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handlerEndExam(Long examId) {
        // 将未交卷的学生，设置为强制交卷
        courseExamPaperStudentManager.updateNotCommitPaperStatus(examId);
        // 更新测评统计数据
        updateStatsData(examId);
        // 发送成绩消息
        CourseExamPaperConfigEntity config = Optional.ofNullable(courseExamPaperConfigManager.findByExamId(examId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评配置不存在"));
        sendScore(config, IsPublishScoreEnum.到截止时间公布, null);
        log.info("考试结束后，强制将未交卷的学生交卷！ examId:{}", examId);
    }

    private void sendScore(CourseExamPaperConfigEntity config, IsPublishScoreEnum isPublishScore, List<Long> studentIds) {
        if (Objects.equals(config.getIsPublishScore(), isPublishScore)) {
            List<Long> list = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(studentIds)) {
                list.addAll(studentIds);
            } else {
                list.addAll(courseExamPaperStudentManager.findStudentIdByExamId(config.getExamPaperId()));
            }
            Spring.committedAsync(() -> {
                // 发送成绩
                chatAssistantExamService.sendMessage(config.getExamPaperId(), list, MessageSubTypeEnum.成绩);
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean manualSupplement(CourseExamPaperStudentManualDTO dto) {
        Long examId = dto.getExamPaperId();
        List<Long> studentIds = dto.getStudentIdList();
        log.info("生成学生试卷，开始。。。examId:{}", examId);
        // 1. 获取试卷信息
        CourseExamPaperEntity exam = getExamPaper(examId);
        CommonErrors.BAD_REQUEST.check(Objects.equals(exam.getStatus(), ExamStatusEnum.已发布)
            || Objects.equals(exam.getStatus(), ExamStatusEnum.已开始), "测评状态错误");
        CourseExamPaperConfigEntity config = Optional.ofNullable(courseExamPaperConfigManager.findByExamId(examId))
            .orElseThrow(() -> CommonErrors.BAD_REQUEST.asException("测评配置不存在"));
        // 2. 获取学生信息
        generateStudentPaper(exam, config, studentIds);
        // 3. 更新测评统计数据
        updateStatsData(examId);
        log.info("生成学生试卷，结束！examId:{}", examId);
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean batchDelUser(CourseExamPaperStudentBatchDelDTO dto) {
        if (dto == null || CollectionUtils.isEmpty(dto.getUserIdList())) {
            return false;
        }
        List<Long> examIdList = courseExamPaperStudentManager.findExamIdByStudentId(dto.getUserIdList());
        Boolean success = courseExamPaperStudentManager.deleteByUserIds(dto.getUserIdList());
        if (success && CollectionUtils.isNotEmpty(examIdList)) {
            // 更新测评统计数据
            examIdList.forEach(this::updateStatsData);
            // 更新测评分析数据
            Spring.committedAsync(() -> {
                // 生成测评分析
                examIdList.forEach(v -> examTopicEventService.publishEvent(ExamQueueEnum.生成测评分析, v));
            });
        }
        return success;
    }

}
